// ContactsManager.cs
//
//  Copyright (C) 2008 Jacek Trubłajewicz
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
//

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using Mono.Unix;
using HAKGERSoft;
using NLog;


namespace wxGadu
{
	public delegate void ContactListChangedEventHandler(object sender, Dictionary<int, ContactInfo> contactList);
	
	
	public class ContactManager
	{
		// this is not used 
		public event ContactListChangedEventHandler contactsChanged;

#if LOG
		private Logger logger = LogManager.GetCurrentClassLogger();
#endif

		/// <summary>
		/// Internal contacts list.
		/// </summary>
		/// <remarks>
		/// Keeps user contacts
		/// </remarks>
		private Dictionary<int, ContactInfo> contacts;
		private AppConfig appConfig;

		/// <summary>
		/// Ctor
		/// </summary>
		/// <param name="app">
		/// A <see cref="AppConfig"/>
		/// application configuration object
		/// </param>
		public ContactManager(AppConfig app)
		{
			contacts = new Dictionary<int, ContactInfo>();
			appConfig = app;
		}

		/// <summary>
		/// Checks if contacts file exists, if not then create empty one
		/// with basic nodes
		/// </summary>
		private void CheckCreateContactsFile()
		{
			try
			{
				if (!Directory.Exists(appConfig.UserDir))
					Directory.CreateDirectory(appConfig.UserDir);
				
				
				if (!File.Exists(appConfig.ContactsFile)) 
				{
#if LOG
					logger.Warn(Catalog.GetString("Contacts file does not exist!\nCreating new one..."));
#endif
	
					XmlWriterSettings settings = new XmlWriterSettings();
					settings.Indent = true;
					settings.ConformanceLevel = ConformanceLevel.Document;
					settings.CloseOutput = false;
					settings.OmitXmlDeclaration = false;
	
					XmlWriter writer = XmlWriter.Create(appConfig.ContactsFile, settings);
					writer.WriteStartDocument();
					writer.WriteComment("wxGadu contacts file");
					writer.WriteStartElement("contacts");
					writer.WriteComment("and all contacts goes here...");
					writer.WriteEndElement();
					writer.Flush();
					writer.Close();
				}
			}
			catch(Exception e)
			{
#if LOG
				logger.FatalException(Catalog.GetString("Unable to create contacts file, exiting..."), e);
#endif
				throw;
			}
		}

		/// <summary>
		/// This will simulate reading contacts from file and
		/// filling list with it.
		/// Function also launches event.
		/// </summary>
		public void ReadContactsFromFile()
		{
			bool change = false;
			XmlDocument doc = new XmlDocument();
			XmlNodeList contactNodes;
			
			try
			{
				doc.Load(appConfig.ContactsFile);
			}
			catch(FileNotFoundException)
			{
				CheckCreateContactsFile();
				return;
			}

			try
			{
				contactNodes = doc.SelectNodes("/contacts/contact");
			}
			catch(NullReferenceException e)
			{
#if LOG
				logger.Trace(String.Format(Catalog.GetString("It seems that there aren't any contacts in file")));
#endif
				return;
			}
#if LOG
			logger.Trace(String.Format(Catalog.GetString("There are {0} nodes"), contactNodes.Count));
#endif
			
			XmlReaderSettings settings = new XmlReaderSettings();
			settings.IgnoreComments = true;
			settings.CloseInput = true;
			
			foreach (XmlNode node in contactNodes)
			{

				ContactInfo ci = new ContactInfo();

				XmlNodeReader nodeReader = new XmlNodeReader(node);
				XmlReader reader = XmlReader.Create(nodeReader, settings);
				string info = string.Empty;

				while (reader.Read()) 
				{					
					if (reader.NodeType == XmlNodeType.Element) 
					{
						switch (reader.Name)
						{
							case "uin": 
								reader.Read();
								if (reader.NodeType == XmlNodeType.Text)
								{
									ci.Uin = reader.ReadContentAsInt();
									info += string.Format("uin: {0} ", ci.Uin);
								}
							break;

							case "name":
								reader.Read();
								if (reader.NodeType == XmlNodeType.Text)
								{
									ci.Name= reader.ReadContentAsString();
									info += string.Format("name: {0} ", ci.Name);
								}
							break;

							case "firstname":
								reader.Read();
								if (reader.NodeType == XmlNodeType.Text)
								{
									ci.Firstname= reader.ReadContentAsString();
									info += string.Format("firstname: {0} ", ci.Firstname);
								}
							break;

							case "lastname":
								reader.Read();
								if (reader.NodeType == XmlNodeType.Text)
								{
									ci.Lastname= reader.ReadContentAsString();
									info += string.Format("lastname: {0} ",  ci.Lastname);
								}
							break;

							case "location":
								reader.Read();
								if (reader.NodeType == XmlNodeType.Text)
								{
									ci.Location= reader.ReadContentAsString();
									info += string.Format("location: {0} ", ci.Location);
								}
							break;

							case "phone":
								reader.Read();
								if (reader.NodeType == XmlNodeType.Text)
								{
									ci.Phone= reader.ReadContentAsString();
									info += string.Format("phone: {0} ", ci.Phone);
								}
							break;
						}
					}					                           
				}
				
				if (!contacts.ContainsKey(ci.Uin) && ci.Uin >= 0 && ci.Name != String.Empty)
				{
					contacts.Add(ci.Uin, ci);
					change = true;
#if LOG
					logger.Trace(string.Format(Catalog.GetString("Loaded contact: {0}"), info));
#endif
				}
				
			}
			
			if (contactsChanged != null && change == true)
				contactsChanged(this, contacts);
		}

		/// <summary>
		/// Writes contact info to contacts file.
		/// </summary>
		/// <param name="contact">
		/// A <see cref="ContactInfo"/>
		/// object containing contact information
		/// </param>
		/// <remarks>
		/// Members value which are stored in contacts file:
		/// uin, name - are obligatory fields
		/// firstname, lastname, location, phone, photo -
		/// are optional fields and if they don't have rational value
		/// they are ommited.
		/// </remarks>
		private void WriteNewContactToFile(ContactInfo contact)
		{
			XmlDocument doc = new XmlDocument();
			XmlNode root;
			
			try
			{
				doc.Load(appConfig.ContactsFile);
			}
			catch(FileNotFoundException)
			{
				CheckCreateContactsFile();
				doc.Load(appConfig.ContactsFile);
			}
						
			try
			{
				root = doc.SelectSingleNode("/contacts");
			}
			catch(NullReferenceException e)
			{
				File.Move(appConfig.ContactsFile, appConfig.ContactsFile + ".error");
#if LOG
				logger.ErrorException(Catalog.GetString("Contacts file has errors, moving it to: contacts.xml.error"), e);
#endif
				CheckCreateContactsFile();
				doc.Load(appConfig.ContactsFile);
				root = doc.SelectSingleNode("/contacts");
			}
			
			XmlElement contacts = doc.CreateElement("contact");
			root.AppendChild(contacts);

			XmlElement uin = contacts.OwnerDocument.CreateElement("uin");
			uin.InnerText = contact.Uin.ToString();
			contacts.AppendChild(uin);
			
			XmlElement name = contacts.OwnerDocument.CreateElement("name");
			name.InnerText = contact.Name;
			contacts.AppendChild(name);

			if (contact.Firstname != String.Empty) 
			{
				XmlElement firstName = contacts.OwnerDocument.CreateElement("firstname");
				firstName.InnerText = contact.Firstname;
				contacts.AppendChild(firstName);				
			}

			if (contact.Lastname != String.Empty) 
			{
				XmlElement lastName = contacts.OwnerDocument.CreateElement("lastname");
				lastName.InnerText = contact.Lastname;
				contacts.AppendChild(lastName);
			}

			if (contact.Location != String.Empty) 
			{
				XmlElement location = contacts.OwnerDocument.CreateElement("location");
				location.InnerText = contact.Location;
				contacts.AppendChild(location);
			}

			if (contact.Phone != String.Empty) 
			{
				XmlElement phone = contacts.OwnerDocument.CreateElement("phone");
				phone.InnerText = contact.Phone;
				contacts.AppendChild(phone);
			}

			doc.Save(appConfig.ContactsFile);
		}

		private void DeleteContactFromFile(int uin)
		{
			XmlDocument doc = new XmlDocument();
			
			try
			{
				doc.Load(appConfig.ContactsFile);
			}
			catch(FileNotFoundException)
			{
				CheckCreateContactsFile();
			}

			try
			{
				XmlNode uinNode = doc.SelectSingleNode(String.Format("//contacts/contact[uin='{0}']", uin));
				uinNode.ParentNode.RemoveChild(uinNode);
				doc.Save(appConfig.ContactsFile);
			}
			catch(NullReferenceException e)
			{
#if LOG
				logger.ErrorException(Catalog.GetString("Contacts file does not contain UIN intended for delete!\nSkipping..."), e);
#endif
			}                             
			
		}
		
		/// <summary>
		/// Adds new contact to internal contact list and 
		/// update contact file.
		/// </summary>
		/// <param name="newContact">
		/// Contact that should be added.
		/// A <see cref="ContactInfo"/>
		/// </param>
		public void AddNewContact(ContactInfo newContact)
		{
			if (contacts.ContainsKey(newContact.Uin))
			{
#if LOG
				logger.Error(String.Format(Catalog.GetString("Contact UIN: {0} is already on the contact list, skipping..."), newContact.Uin));			
#endif
				return;
			}
			
			contacts.Add(newContact.Uin, newContact);
			WriteNewContactToFile(newContact);
		}
		
		/// <summary>
		/// Deletes contact from internal contacts list and from contacts file
		/// </summary>
		/// <param name="uin">
		/// A <see cref="System.Int32"/>
		/// </param>
		public void DeleteContact(int uin)
		{
			DeleteContactFromFile(uin);
			contacts.Remove(uin);
		}

		/// <summary>
		/// Updates contact information in contacts file.
		/// </summary>
		/// <param name="uin">
		/// A <see cref="System.Int32"/>
		/// UIN of contact which information should be updated
		/// in contacts file.
		/// </param>
		/// <remarks>
		/// This version can be used if contact's UIN hasn't changed.
		/// <c>ContactInfo</c> object connected to given <c>uin</c>
		/// on <c>ContactManager.Contacts</c> dictionary
		/// should be updated to new values of members before calling
		/// this method.
		/// </remarks>
		public void UpdateContactInFile(int uin)
		{
			//FIXME: this is dangerous, contact can be deleted and then if 
			// write error occurs it won't be added again
			// better way is to find contact node and update it values
			DeleteContactFromFile(uin);
			WriteNewContactToFile(contacts[uin]);
		}
		
		/// <summary>
		/// Updates contact information in contacts file.
		/// </summary>
		/// <param name="newContactData">
		/// A <see cref="ContactInfo"/>
		/// object containig new contact information.
		/// </param>
		/// <param name="oldUin">
		/// A <see cref="System.Int32"/>
		/// old (the previous one) contact's UIN.
		/// </param>
		/// <remarks>
		/// This version should be used when contact's UIN
		/// has been changed
		/// </remarks>
		public void UpdateContactInFile(ContactInfo newContactData, int oldUin)
		{
			//FIXME: this is dangerous, contact can be deleted and then if 
			// write error occurs it won't be added again
			// better way is to find contact node and update it values
			//contacts.Remove(oldUin);
			DeleteContactFromFile(oldUin);
			
			//contacts.Add(newContactData.Uin, newContactData);
			WriteNewContactToFile(newContactData);
		}


		public static string StatusToString(int ggStatus)
		{
			switch (ggStatus)
			{
				case StatusConstans.online:
					return Catalog.GetString("Online");
						
				case StatusConstans.onlineDescr:
					return Catalog.GetString("Online with description");
	
				case StatusConstans.away:
					return Catalog.GetString("Away");
					
				case StatusConstans.awayDescr:
					return Catalog.GetString("Away with description");
	
				case StatusConstans.invisible:
					return Catalog.GetString("Invisible");
	
				case StatusConstans.invisibleDescr:
					return Catalog.GetString("Invisible with description");
	
				case StatusConstans.offline:
					return Catalog.GetString("Offline");
	
				case StatusConstans.offlineDescr:
					return Catalog.GetString("Offline with description");
				
				default:
					return String.Empty;
			}
		}

		//// <value>
		/// Contact list
		/// </value>
		public Dictionary<int, ContactInfo> Contacts
		{
			get { return contacts; }
		}


		/// <summary>
		/// Saves position of each contact on <c>Gadu.Users</c> 
		/// list into <c>ContactInfo.Position</c>
		///  field of each on this.contacts
		/// dictionary item.
		/// </summary>
		/// <remarks>
		/// Both list must have the same items count. Every UIN that is on the "users"
		/// list must be present on this.contacts dictionary.
		/// </remarks>
		/// <exception cref="ArgumentOutOfRangeException">
		/// Thrown when users.Count != contacts.Count
		/// </exception>
		/// <exception cref="ArgumentException">
		/// Thrown when item UIN exists on "users" list but do not exists on this.contacts dictionary.
		/// </exception>
		/// <param name="users">
		/// A <see cref="GGUsers"/>
		/// </param>
		public void SyncPositions(GGUsers users)
		{
			if (users.Count != contacts.Count)
				throw new ArgumentOutOfRangeException("Items count in Gadu.Users and ContactManager.Contacts is different!");
			
			for (int i = 0; i < users.Count; i++)
			{
				try
				{
					int num = users[i].GGNumber;
					//System.Console.WriteLine("num = " + num);
					contacts[num].Position = i;
				}
				catch(ArgumentException)
				{
					System.Console.WriteLine("ContactManager.Contacts and Gadu.Users lists are not synchronized!");
					throw;
				}
			}
		}
	}
}
